/*  
 * Actipro Software's WPF Docking & MDI / Prism Integration Add-on
 * http://actipro.codeplex.com/
 * 
 * Copyright (c) 2009-2015 Actipro Software LLC. 
 *  
 * This source code is subject to the terms of the Microsoft Public License (Ms-PL). 
 *  
 * Redistribution and use in source and binary forms, with or without modification, 
 * is permitted provided that redistributions of the source code retain the above 
 * copyright notices and this file header. 
 *  
 * Additional copyright notices should be appended to the list above. 
 * 
 * For details, see <http://www.opensource.org/licenses/ms-pl.html>. 
 *  
 * All other rights reserved. 
 *
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using Microsoft.Practices.Prism.Regions;

namespace ActiproSoftware.Windows.Prism.Regions {

	/// <summary>
	/// Implementation of <see cref="IViewsCollection"/> that takes an <see cref="ObservableCollection{T}"/> of <see cref="ItemMetadata"/>
	/// which can be used to filter/sort items.
	/// </summary>
	public class DockSiteRegionViewsCollection : IEnumerable<object>, IEnumerable, INotifyCollectionChanged, IViewsCollection {

		private ObservableCollection<ItemMetadata> collection;
		private EnumerableView<ItemMetadata> enumerableView;
		private List<ItemMetadata> monitoredItems;

		private Comparison<object> sortComparison;

		/////////////////////////////////////////////////////////////////////////////////////////////////////
		#region OBJECT
		/////////////////////////////////////////////////////////////////////////////////////////////////////

		/// <summary>
		/// Initializes a new instance of the <see cref="DockSiteRegionViewsCollection"/> class.
		/// </summary>
		/// <param name="collection">The collection to wrap.</param>
		/// <param name="filter">The filter logic.</param>
		public DockSiteRegionViewsCollection(ObservableCollection<ItemMetadata> collection, Predicate<ItemMetadata> filter) {
			if (collection == null)
				throw new ArgumentNullException("collection");

			this.enumerableView = new EnumerableView<ItemMetadata>(collection, filter);
			this.enumerableView.CollectionChanged += this.OnEnumerableViewCollectionChanged;
			this.monitoredItems = new List<ItemMetadata>();

			this.collection = collection;
			this.collection.CollectionChanged += this.OnWrappedCollectionChanged;

			this.AttachMetadataChangedHandlers();
		}

		#endregion // OBJECT

		/////////////////////////////////////////////////////////////////////////////////////////////////////
		#region INTERFACE IMPLEMENTATION
		/////////////////////////////////////////////////////////////////////////////////////////////////////

		#region IEnumerable<object> Members

		/// <summary>
		/// Returns an enumerator that iterates through the collection.
		/// </summary>
		/// <returns>A <see cref="IEnumerator{Object}"/> that can be used to iterate through the collection.</returns>
		public IEnumerator<object> GetEnumerator() {
			return this.enumerableView.Select(m => m.Item).ToList().GetEnumerator();
		}

		#endregion // IEnumerable<object> Members

		#region IEnumerable Members

		/// <summary>
		/// Returns an enumerator that iterates through a collection.
		/// </summary>
		/// <returns>
		/// An <see cref="IEnumerator"/> object that can be used to iterate through the collection.
		/// </returns>
		IEnumerator IEnumerable.GetEnumerator() {
			return ((IEnumerable)this.enumerableView.Select(m => m.Item).ToList()).GetEnumerator();
		}

		#endregion // IEnumerable Members

		#region INotifyCollectionChanged Members

		/// <summary>
		/// Occurs when the collection changes.
		/// </summary>
		public event NotifyCollectionChangedEventHandler CollectionChanged;

		#endregion // INotifyCollectionChanged Members

		#region IViewsCollection Members

		/// <summary>
		/// Determines whether the collection contains a specific value.
		/// </summary>
		/// <param name="value">The object to locate in the collection.</param>
		/// <returns>
		/// <c>true</c> if <paramref name="value"/> is found in the collection; otherwise, <c>false</c>.
		/// </returns>
		public bool Contains(object value) {
			return this.enumerableView.Any(m => m.Item == value);
		}

		#endregion // IViewsCollection Members

		#endregion // INTERFACE IMPLEMENTATION

		/////////////////////////////////////////////////////////////////////////////////////////////////////
		#region NON-PUBLIC PROCEDURES
		/////////////////////////////////////////////////////////////////////////////////////////////////////

		/// <summary>
		/// Attaches an event handler to the <c>MetadataChanged</c> event of the specified metadata.
		/// </summary>
		/// <param name="metadata">The metadata to which the handler should be added.</param>
		private void AttachMetadataChangedHandler(ItemMetadata metadata) {
			if (metadata == null || this.monitoredItems.Contains(metadata))
				return;

			metadata.MetadataChanged += this.OnItemMetadataChanged;
			this.monitoredItems.Add(metadata);
		}

		/// <summary>
		/// Attaches an event handler to the <c>MetadataChanged</c> event of all the associated metadata objects.
		/// </summary>
		private void AttachMetadataChangedHandlers() {
			this.DetachMetadataChangedHandlers();

			foreach (ItemMetadata metadata in this.collection) {
				metadata.MetadataChanged += this.OnItemMetadataChanged;
				this.monitoredItems.Add(metadata);
			}
		}

		/// <summary>
		/// Attaches an event handler to the <c>MetadataChanged</c> event of all the specified metadata objects.
		/// </summary>
		/// <param name="items">The metadata items to which the handler should be added.</param>
		private void AttachMetadataChangedHandlers(IList items) {
			if (items == null)
				return;

			foreach (object obj in items)
				this.AttachMetadataChangedHandler(obj as ItemMetadata);
		}

		/// <summary>
		/// Detaches an event handler from the <c>MetadataChanged</c> event of the specified metadata.
		/// </summary>
		/// <param name="metadata">The metadata to which the handler should be removed.</param>
		private void DetachMetadataChangedHandler(ItemMetadata metadata) {
			if (metadata == null || !this.monitoredItems.Contains(metadata))
				return;

			metadata.MetadataChanged -= this.OnItemMetadataChanged;
			this.monitoredItems.Remove(metadata);
		}

		/// <summary>
		/// Detaches an event handler from the <c>MetadataChanged</c> event of all the associated metadata objects.
		/// </summary>
		private void DetachMetadataChangedHandlers() {
			foreach (ItemMetadata metadata in this.monitoredItems)
				metadata.MetadataChanged -= this.OnItemMetadataChanged;
			this.monitoredItems.Clear();
		}

		/// <summary>
		/// Detaches an event handler from the <c>MetadataChanged</c> event of all the specified metadata objects.
		/// </summary>
		/// <param name="items">The metadata items to which the handler should be removed.</param>
		private void DetachMetadataChangedHandlers(IList items) {
			if (items == null)
				return;

			foreach (object obj in items)
				this.DetachMetadataChangedHandler(obj as ItemMetadata);
		}

		/// <summary>
		/// Raises the <see cref="CollectionChanged"/> event.
		/// </summary>
		/// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
		private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e) {
			NotifyCollectionChangedEventHandler handler = this.CollectionChanged;
			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Handles the <c>CollectionChanged</c> event of the <see cref="enumerableView"/>.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
		private void OnEnumerableViewCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
			List<object> oldItems = null;
			if (e.OldItems != null)
				oldItems = e.OldItems.Cast<ItemMetadata>().Select(m => m.Item).ToList();

			List<object> newItems = null;
			if (e.NewItems != null)
				newItems = e.NewItems.Cast<ItemMetadata>().Select(m => m.Item).ToList();

			NotifyCollectionChangedEventArgs eventArgs = null;
			switch (e.Action) {
				case NotifyCollectionChangedAction.Add:
					eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems, e.NewStartingIndex);
					break;
				case NotifyCollectionChangedAction.Move:
					eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, oldItems, e.NewStartingIndex, e.OldStartingIndex);
					break;
				case NotifyCollectionChangedAction.Remove:
					eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems, e.OldStartingIndex);
					break;
				case NotifyCollectionChangedAction.Replace:
					eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, newItems, oldItems, e.OldStartingIndex);
					break;
				case NotifyCollectionChangedAction.Reset:
				default:
					eventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
					break;
			}

			this.NotifyCollectionChanged(eventArgs);
		}

		/// <summary>
		/// Handles the <c>MetadataChanged</c> event of an associated <see cref="ItemMetadata"/>.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
		private void OnItemMetadataChanged(object sender, EventArgs e) {
			ItemMetadata metadata = sender as ItemMetadata;
			if (metadata != null)
				enumerableView.Reevaluate(metadata);
		}

		/// <summary>
		/// Handles the <c>CollectionChanged</c> event of the <see cref="collection"/>.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
		private void OnWrappedCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
			switch (e.Action) {
				case NotifyCollectionChangedAction.Add:
					this.AttachMetadataChangedHandlers(e.NewItems);
					break;
				case NotifyCollectionChangedAction.Remove:
					this.DetachMetadataChangedHandlers(e.OldItems);
					break;
				case NotifyCollectionChangedAction.Replace:
					this.DetachMetadataChangedHandlers(e.OldItems);
					this.AttachMetadataChangedHandlers(e.NewItems);
					break;
				case NotifyCollectionChangedAction.Reset:
					this.DetachMetadataChangedHandlers();
					break;
			}
		}

		#endregion // NON-PUBLIC PROCEDURES

		/////////////////////////////////////////////////////////////////////////////////////////////////////
		#region PUBLIC PROCEDURES
		/////////////////////////////////////////////////////////////////////////////////////////////////////

		/// <summary>
		/// Gets or sets the comparison used to sort the views.
		/// </summary>
		/// <value>The comparison to use.</value>
		public Comparison<object> SortComparison {
			get {
				return this.sortComparison;
			}
			set {
				if (this.sortComparison != value) {
					this.sortComparison = value;

					if (this.sortComparison != null)
						this.enumerableView.SortComparison = (x, y) => { return this.sortComparison(x.Item, y.Item); };
					else
						this.enumerableView.SortComparison = null;
				}
			}
		}

		#endregion // PUBLIC PROCEDURES
	}
}